package ift605.tp3.b;

import ift605.tp3.b.resolvers.ResolverFactory;
import ift605.tp3.utils.IDGenerator;
import ift605.tp3.utils.equations.EquationUtility;
import ift605.tp3.utils.equations.IEquation;
import ift605.tp3.utils.equations.UnknownEquationTypeException;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.wrapper.AgentController;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

public class DispatcherAgent extends Agent
{
    private static final long serialVersionUID = 1L;
    
    private final Map<String, AID> tickets = new HashMap<String, AID>();
    private final Map<String, Queue<AID>> resolvers = new HashMap<String, Queue<AID>>();
    private int count = 0;
    
    @Override
    protected void setup()
    {
        System.out.println(getLocalName() + " is here, ready to dispatch !!");
        try
        {
            final DFAgentDescription dfd = new DFAgentDescription();
            dfd.setName(getAID());
            DFService.register(this, dfd);
            
            addBehaviour(new CyclicBehaviour(this)
            {
                private static final long serialVersionUID = 1L;
                
                @Override
                public void action()
                {
                    final ACLMessage msg = receive();
                    
                    if (msg != null)
                    {
                        switch (msg.getPerformative())
                        {
                            case ACLMessage.CANCEL: // Time to kill everyone !
                            {
                                System.out.println("<" + getLocalName() + "> received EXIT signal");
                                final ACLMessage exit = new ACLMessage(ACLMessage.CANCEL);
                                for (final String key : resolvers.keySet())
                                {
                                    final Queue<AID> queue = resolvers.get(key);
                                    while (queue != null && !queue.isEmpty())
                                    {
                                        exit.addReceiver(queue.poll());
                                    }
                                }
                                send(exit);
                                doDelete();
                                break;
                            }
                            case ACLMessage.PROPOSE: // We got an agent ready to work !!
                            {
                                final String type = msg.getContent();
                                System.out.println("<" + getLocalName() + "> received READY signal from " + msg.getSender().getLocalName() + "for " + type);
                                if (!resolvers.containsKey(type) || resolvers.get(type) == null)
                                {
                                    final Queue<AID> queue = new LinkedList<AID>();
                                    queue.add(msg.getSender());
                                    resolvers.put(type, queue);
                                }
                                else
                                {
                                    resolvers.get(type).add(msg.getSender());
                                }
                                break;
                            }
                            case ACLMessage.REQUEST: // Yay ! We have something to do
                            {
                                final String ticket = IDGenerator.generate();
                                final String eq = msg.getContent();
                                System.out.println("<" + getLocalName() + "> " + msg.getSender().getLocalName() + " generate a ticket (" + ticket + ") for his request: " + eq);
                                try
                                {
                                    // This try the equation if it's working
                                    final IEquation equation = EquationUtility.parse(eq);
                                    final String type = equation.getClass().getName();
                                    System.out.println("<" + getLocalName() + "> This is a valid equation: " + type + "(y = " + equation.getUserReadableString() + ")");
                                    
                                    AID id;
                                    if (!resolvers.containsKey(type) || resolvers.get(type) == null || resolvers.get(type).isEmpty())
                                    {
                                        // Starts a new agent
                                        final String path = ResolverFactory.getAgentPath(type);
                                        if (path == null)
                                        {
                                            throw new UnknownEquationTypeException();
                                        }
                                        count++;
                                        final String localName = count + "_" + type;
                                        final AgentController resolver = getContainerController().createNewAgent(localName, path, null);
                                        resolver.start();
                                        id = new AID(localName, AID.ISLOCALNAME);
                                    }
                                    else
                                    {
                                        id = resolvers.get(type).poll();
                                    }
                                    
                                    final ACLMessage request = new ACLMessage(ACLMessage.REQUEST);
                                    request.setContent(ticket);
                                    request.addReceiver(id);
                                    send(request);
                                    final ACLMessage confirmation = blockingReceive(MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.CONFIRM), MessageTemplate.MatchPerformative(ACLMessage.DISCONFIRM)));
                                    if (confirmation.getPerformative() == ACLMessage.DISCONFIRM)
                                    {
                                        throw new UnknownEquationTypeException();
                                    }
                                    final ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
                                    inform.setContent(eq);
                                    inform.addReceiver(id);
                                    send(inform);
                                    
                                    // Let's confirm to the asker
                                    final ACLMessage confirm = new ACLMessage(ACLMessage.CONFIRM);
                                    confirm.setContent(ticket);
                                    confirm.addReceiver(msg.getSender());
                                    send(confirm);
                                    
                                    tickets.put(ticket, msg.getSender());
                                }
                                catch (final Exception e)
                                {
                                    final ACLMessage confirm = new ACLMessage(ACLMessage.DISCONFIRM);
                                    confirm.addReceiver(msg.getSender());
                                    send(confirm);
                                }
                                break;
                            }
                            case ACLMessage.FAILURE: // Booooooooo! :(
                            {
                                final String ticket = msg.getContent();
                                System.out.println("<" + getLocalName() + "> received failure for: " + ticket);
                                if (tickets.containsKey(ticket))
                                {
                                    final ACLMessage res = new ACLMessage(ACLMessage.FAILURE);
                                    res.setContent(ticket);
                                    res.addReceiver(tickets.get(ticket));
                                    send(res);
                                }
                                break;
                            }
                            case ACLMessage.INFORM: // We got results! :D
                            {
                                final String ticket = msg.getContent();
                                System.out.println("<" + getLocalName() + "> received success for: " + ticket);
                                if (tickets.containsKey(ticket))
                                {
                                    final ACLMessage res = new ACLMessage(ACLMessage.AGREE);
                                    res.setContent(ticket);
                                    res.addReceiver(msg.getSender());
                                    send(res);
                                    final ACLMessage gotresult = blockingReceive(MessageTemplate.MatchPerformative(ACLMessage.INFORM));
                                    final String derive = gotresult.getContent();
                                    
                                    final ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
                                    inform.setContent(derive);
                                    inform.addReceiver(tickets.get(ticket));
                                    send(inform);
                                }
                                else
                                {
                                    final ACLMessage res = new ACLMessage(ACLMessage.REFUSE);
                                    res.setContent(ticket);
                                    res.addReceiver(msg.getSender());
                                    send(res);
                                }
                                
                                break;
                            }
                        }
                    }
                    else
                    {
                        // if no message is arrived, block the behaviour
                        block();
                    }
                }
            });
        }
        catch (final Exception e)
        {
            System.out.println("This is too much for this poor dispatcher: " + e);
            e.printStackTrace();
        }
    }
}
